考虑用静态工厂方法代替构造器

创建类实例的方法

  • 公有构造器
  • 公有静态工厂方法

静态工厂方法与构造器相比的优势

  • 静态工厂方法有名称

    如果构造器的参数本身没有确切的描述正被返回的对象,那么具有适当名称的静态工厂会更容易使用,举个例子,构造器BigInteger(int, int, Random)返回的BigInteger可能为素数,如果用名为BigInteger.probablePrime的静态工厂方法来表示,显然更加清楚。

    另外,当一个类需要多个带有相同签名的构造器时,就用静态工厂方法代替构造器。

  • 不必在每次调用它们的时候都创建一个新对象

  • 可以返回原返回类型的任何子类型的对象

  • 在创建参数化类型实例的时候,它们使代码变得更加简洁

    在调用参数化类的构造器时,即使类型参数很明显,也必须指明,这通常要求你接连两次提供类型参数:

    1
    Map<String, List<String>> m = new HashMap<String, List<String>>();

    随着类型参数变得越来越长,越来越复杂,这显然是不合适的。但是有了静态工厂方法,编译器就可以替你找到类型参数,这就是类型推导:

    1
    2
    3
    public static <K, V> HashMap<K, V> newInstance() {
    return new HashMap<K, V>();
    }

    你就可以用下面简洁的代码代替上面这段繁琐的声明:

    1
    Map<String, List<String>> m = HashMap.newInstance();

静态工厂方法的一些惯用名称

  • valueOf

    该方法返回的实例与它的参数具有相同的值,这样的静态工厂方法实际上是类型转换方法

  • of

    valueOf的一种更加简洁的替代

  • getInstance

    返回的实例是通过方法的参数来描述的,但是不能够说与参数具有相同的值,对于Singleton来说,该方法没有参数,并返回唯一的实例

  • newInstance

    与getInstance实例一样,但newInstance能够确保返回的每个实例都与所有其他实例不同

遇到多个构造器参数时要考虑用构建器

当构造器参数很多时,重叠构造器模式可行,但是当有许多参数的时候,客户端代码会很难写,并且难以阅读。如果读者想知道那些值是什么意思,必须很仔细的数着这些参数来一探究竟,更为可怕的是,一长串相同类型的参数会导致一些微妙的错误,如果客户端不小心颠倒了其中两个参数的顺序,编译器并不会报错,但是在程序运行的时候会出现错误,这种错误往往还比较难以发现。

遇到许多构造器参数的时候,还可以使用JavaBeans模式,在这种模式下,调用一个无参构造器来创建对象,然后调用setter方法来设置每个必要的参数。JavaBeans模式最大的问题就是阻止了把类做成不可变的可能,需要程序员付出额外的努力来确保它的线程安全。

对于多个构造器参数还有第三种方法,即Builder模式。我们先看代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
public class NutritionFacts {
private final int servingSize;
private final int servings;
private final int calories;
private final int fat;
private final int sodium;
private final int carbonhydrate;
public static class Builder {
// Required parameters
private final int servingSize;
private final int servings;
// Optional parameters - initialized to default values
private int calories = 0;
private int fat = 0;
private int carbonhydrate = 0;
private int sodium = 0;
public Builder(int servingSize, int servings) {
this.servingSize = servingSize;
this.servings = servings;
}
public Builder calories(int val) {
calories = val;
return this;
}
public Builder fat(int val) {
fat = val;
return this;
}
public Builder carbonhydrate(int val) {
carbonhydrate = val;
return this;
}
public Builder sodium(int val) {
sodium = val;
return this;
}
public NutritionFacts build() {
return new NutritionFacts(this);
}
}
private NutritionFacts(Builder builder) {
servingSize = builder.servingSize;
servings = builder.servings;
calories = builder.calories;
fat = builder.fat;
sodium = builder.sodium;
carbonhydrate = builer.carbonhydrate;
}
}

注意NutritionFacts是不可变的,下面是客户端代码:

1
NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8).calories(100).sodium(35).carbonhydrate(27).build();

如果类的构造器或者静态工厂中具有多个参数,设计这种类时,Builder模式就是种不错的选择,特别是当大多数参数可选的时候。